using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using Microsoft.Surface;
using Microsoft.Surface.Presentation;
using Microsoft.Surface.Presentation.Controls;
using System.Timers;

namespace LetsGoToSchool
{
    /// <summary>
    /// Interaction logic for MemoryGame.xaml
    /// </summary>
    /// 
    
    public partial class MemoryGame : SurfaceWindow
    {
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// 
        private List<MemorySessionNode> nodeList;
        private List<Card> _cards;
        private Card _card1, _card2;
        private Boolean _gamePlayed;
        private DispatcherTimer _timer;
        private int _level = 1;
        private List<String> _images;
        private MemorySessionNode _resultsNode;
        private int _numMoves = 0, _ticks = 0;
        private DateTime _startTime, _endTime;
        private List<String> _imList;

        public MemoryGame()
        {
            InitializeComponent();

            nodeList = new List<MemorySessionNode>();

            // Add handlers for Application activation events
            AddActivationHandlers();
            _memory.setMain(this);

            this.loadImages("food");
            _cards = new List<Card>();

            _card1 = null;
            _card2 = null;
            _timer = new DispatcherTimer();
            _timer.Interval = TimeSpan.FromMilliseconds(800);
            _timer.Tick += OnTimedEvent;
            _timer.Start();
        }

        /// <summary>
        /// Loads the images corresponding to the selected theme
        /// </summary>
        /// <param name="theme"></param>
        public void loadImages(String theme)
        {
            _images = new List<String>();
            // get all the images from the database
            _imList = Database.getAllMyImages(theme);

            // add all the file paths to a list of paths
            for (int i = 0; i < _imList.Count; i++)
            {
                _images.Add(_imList[i]);
            }
        }

        /// <summary>
        /// Sets the level of difficulty for the game 
        /// </summary>
        /// <param name="level"></param>
        public void setLevel(int level)
        {
            _level = level;
        }

        /// <summary>
        /// Sets up the board (with cards of the selected theme) to be displayed when the level of difficulty is "Easy"
        /// </summary>
        /// <param name="theme"></param>
        public void setUpLevel1(String theme)
        {
            Board.Children.Clear();
            
            // level one means 8 cards --> space them out accordingly
            // images are 175 by 175
            // layout is 2 by 4
            // height is 768, width is 1024
            this.loadImages(theme);
            List<Point> level1_points = new List<Point>();
            _cards = new List<Card>();
            int imageH = 175;
            int imageW = 175;

            double spaceY = (768 - (2 * imageH)) / 3;
            double spaceX = (824 - (4 * imageW)) / 5;

            level1_points.Add(new Point(spaceX, spaceY));
            level1_points.Add(new Point(spaceX + imageW + spaceX, spaceY));
            level1_points.Add(new Point(2 * (spaceX + imageW) + spaceX, spaceY));
            level1_points.Add(new Point(3 * (spaceX + imageW) + spaceX, spaceY));
            level1_points.Add(new Point(spaceX, imageH + 2 * spaceY));
            level1_points.Add(new Point(spaceX + imageW + spaceX, imageH + 2 * spaceY));
            level1_points.Add(new Point(2 * (spaceX + imageW) + spaceX, imageH + 2 * spaceY));
            level1_points.Add(new Point(3 * (spaceX + imageW) + spaceX, imageH + 2 * spaceY));

            // lay out the cards in the appropriate positions
            this.boardSetUp(4, imageH, imageW, level1_points);
        }

        /// <summary>
        /// Lays out the cards at the positions passed in
        /// </summary>
        /// <param name="numPairs"></param> the number of pairs on the board
        /// <param name="imageH"></param> the pixel height of the card
        /// <param name="imageW"></param>the pixel width of the card
        /// <param name="points"></param> the pixel point locations for the cards 
        public void boardSetUp(int numPairs, int imageH, int imageW, List<Point> points)
        {
            Boolean remove = (_images.Count >= numPairs);
            Random r = new Random();

            for (int i = 0; i < numPairs; i++)
            {
                // first choose a random card image
                int rand_img_index = r.Next(_images.Count);

                // create two new cards based on that image
                Card cardA = new Card(_images[rand_img_index], this);
                cardA.setDim(imageH, imageW);
                Card cardB = new Card(_images[rand_img_index], this);
                cardB.setDim(imageH, imageW);

                // remove that image from the list of images only if there are enough unique images
                if (remove)
                {
                    _images.RemoveAt(rand_img_index);
                }

                // now choose a random location for each of the two cards that were just created
                int rand_loc_index1 = r.Next(points.Count);
                cardA.addToCanvas(Board, points[rand_loc_index1]);
                points.RemoveAt(rand_loc_index1);

                int rand_loc_index2 = r.Next(points.Count);
                cardB.addToCanvas(Board, points[rand_loc_index2]);
                points.RemoveAt(rand_loc_index2);

                // add the cards to the list of cards being displayed on the board
                _cards.Add(cardA);
                _cards.Add(cardB);
            }
        }

        /// <summary>
        /// Sets up the board (with cards of the selected theme) to be displayed when the level of difficulty is "Medium"
        /// </summary>
        /// <param name="theme"></param>
        public void setUpLevel2(String theme)
        {
            Board.Children.Clear();
            // level two means 12 cards --> space them out accordingly
            // images are 150 by 150
            // layout is 3 by 4
            // height is 768, width is 1024

            List<Point> points = new List<Point>();
            this.loadImages(theme);
            _cards = new List<Card>();

            int imageH = 150;
            int imageW = 150;

            double spaceY = (768 - (3 * imageH)) / 4;
            double spaceX = (824 - (4 * imageW)) / 5;

            points.Add(new Point(spaceX, spaceY));
            points.Add(new Point(spaceX + imageW + spaceX, spaceY));
            points.Add(new Point(2 * (spaceX + imageW) + spaceX, spaceY));
            points.Add(new Point(3 * (spaceX + imageW) + spaceX, spaceY));

            points.Add(new Point(spaceX, imageH + 2 * spaceY));
            points.Add(new Point(spaceX + imageW + spaceX, imageH + 2 * spaceY));
            points.Add(new Point(2 * (spaceX + imageW) + spaceX, imageH + 2 * spaceY));
            points.Add(new Point(3 * (spaceX + imageW) + spaceX, imageH + 2 * spaceY));

            points.Add(new Point(spaceX, 2 * imageH + 3 * spaceY));
            points.Add(new Point(spaceX + imageW + spaceX, 2 * imageH + 3 * spaceY));
            points.Add(new Point(2 * (spaceX + imageW) + spaceX, 2 * imageH + 3 * spaceY));
            points.Add(new Point(3 * (spaceX + imageW) + spaceX, 2 * imageH + 3 * spaceY));

            // layout the cards in the appropriate positions
            this.boardSetUp(6, imageH, imageW, points);
        }

        /// <summary>
        /// Sets up the board (with cards of the selected theme) to be displayed when the level of difficulty is "Hard"
        /// </summary>
        /// <param name="theme"></param>
        public void setUpLevel3(String theme)
        {
            Board.Children.Clear();
            // level two means 16 cards --> space them out accordingly
            // images are 125 by 125
            // layout is 4 by 4
            // height is 768, width is 1024

            List<Point> points = new List<Point>();
            this.loadImages(theme);
            _cards = new List<Card>();

            int imageH = 125;
            int imageW = 125;

            double spaceY = (768 - (4 * imageH)) / 5;
            double spaceX = (824 - (4 * imageW)) / 5;

            points.Add(new Point(spaceX, spaceY));
            points.Add(new Point(spaceX + imageW + spaceX, spaceY));
            points.Add(new Point(2 * (spaceX + imageW) + spaceX, spaceY));
            points.Add(new Point(3 * (spaceX + imageW) + spaceX, spaceY));

            points.Add(new Point(spaceX, imageH + 2 * spaceY));
            points.Add(new Point(spaceX + imageW + spaceX, imageH + 2 * spaceY));
            points.Add(new Point(2 * (spaceX + imageW) + spaceX, imageH + 2 * spaceY));
            points.Add(new Point(3 * (spaceX + imageW) + spaceX, imageH + 2 * spaceY));

            points.Add(new Point(spaceX, 2 * imageH + 3 * spaceY));
            points.Add(new Point(spaceX + imageW + spaceX, 2 * imageH + 3 * spaceY));
            points.Add(new Point(2 * (spaceX + imageW) + spaceX, 2 * imageH + 3 * spaceY));
            points.Add(new Point(3 * (spaceX + imageW) + spaceX, 2 * imageH + 3 * spaceY));

            points.Add(new Point(spaceX, 3 * imageH + 4 * spaceY));
            points.Add(new Point(spaceX + imageW + spaceX, 3 * imageH + 4 * spaceY));
            points.Add(new Point(2 * (spaceX + imageW) + spaceX, 3 * imageH + 4 * spaceY));
            points.Add(new Point(3 * (spaceX + imageW) + spaceX, 3 * imageH + 4 * spaceY));

            // lays out the cards in the appropriate positions
            this.boardSetUp(8, imageH, imageW, points);

        }

        /// <summary>
        /// Sets visibility of objects for the start of the game
        /// </summary>
        public void startGame()
        {
            MemoryCanvas.Visibility = Visibility.Visible;
            Board.Visibility = Visibility.Visible;
            end_message.Visibility = Visibility.Hidden;
            _resultsNode = new MemorySessionNode();
            _resultsNode.setDifficulty(_level - 1);
            _startTime = DateTime.Now;
        }

        /// <summary>
        /// Called each time the timer ticks
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnTimedEvent(object sender, EventArgs e)
        {
            _ticks++;
            _timer.Stop();
            if (!this.isEndOfGame() && _card1 != null && _card2 != null)
            {
                _numMoves++;
                _gamePlayed = true;
                System.Threading.Thread.Sleep(800);
                if (this.isPair(_card1, _card2))
                {
                    this.removeCards(_card1, _card2);
                }
                else
                {
                    _card1.flipOver();
                    _card2.flipOver();
                }
                _card1 = null;
                _card2 = null;
            }
            else if (this.isEndOfGame() && _gamePlayed)
            {
                end_message.Visibility = Visibility.Visible;
                _gamePlayed = false;
            }
            _timer.Start();
        }

        /// <summary>
        /// Returns the first flipped over card
        /// </summary>
        private Card getCard1()
        {
            return _card1;
        }

        /// <summary>
        /// Returns the second flipped over card
        /// </summary>
        private Card getCard2()
        {
            return _card2;
        }

        /// <summary>
        /// Sets the flipped over card
        /// </summary>
        /// <param name="c"></param>
        private void setCard(Card c)
        {

            if (_card1 == null)
            {
                _card1 = c;
            }
            else if (_card2 == null)
            {
                _card2 = c;
            }
        }

        /// <summary>
        /// Returns whether two cards are flipped over
        /// </summary>
        public Boolean are2CardsUp()
        {
            return (_card1 != null && _card2 != null);
        }

        /// <summary>
        /// Returns whether the two cards passed in are of the same type
        /// </summary>
        /// <param name="card1"></param>
        /// <param name="card2"></param>
        private Boolean isPair(Card card1, Card card2)
        {
            if (card1.getFile() == card2.getFile())
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Removes the two cards from the displayed board
        /// </summary>
        /// <param name="card1"></param>
        /// <param name="card2"></param>
        private void removeCards(Card card1, Card card2)
        {
            _cards.Remove(card1);
            _cards.Remove(card2);
            card1.removeFromCanvas(Board);
            card2.removeFromCanvas(Board);
        }

        /// <summary>
        /// Determines whether the game is over
        /// </summary>
        /// <param name="card1"></param>
        public Boolean isEndOfGame()
        {
            return (_cards.Count == 0);
        }

        /// <summary>
        /// Occurs when the window is about to close. 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);

            foreach (MemorySessionNode row in nodeList)
            {
                row.storeToDatabase();
            }

            // Remove handlers for Application activation events
            RemoveActivationHandlers();
        }

        /// <summary>
        /// Adds handlers for Application activation events.
        /// </summary>
        private void AddActivationHandlers()
        {
            // Subscribe to surface application activation events
            ApplicationLauncher.ApplicationActivated += OnApplicationActivated;
            ApplicationLauncher.ApplicationPreviewed += OnApplicationPreviewed;
            ApplicationLauncher.ApplicationDeactivated += OnApplicationDeactivated;
        }

        /// <summary>
        /// Removes handlers for Application activation events.
        /// </summary>
        private void RemoveActivationHandlers()
        {
            // Unsubscribe from surface application activation events
            ApplicationLauncher.ApplicationActivated -= OnApplicationActivated;
            ApplicationLauncher.ApplicationPreviewed -= OnApplicationPreviewed;
            ApplicationLauncher.ApplicationDeactivated -= OnApplicationDeactivated;
        }

        /// <summary>
        /// This is called when application has been activated.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnApplicationActivated(object sender, EventArgs e)
        {
            //TODO: enable audio, animations here
        }

        /// <summary>
        /// This is called when application is in preview mode.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnApplicationPreviewed(object sender, EventArgs e)
        {
            //TODO: Disable audio here if it is enabled

            //TODO: optionally enable animations here
        }

        /// <summary>
        ///  This is called when application has been deactivated.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnApplicationDeactivated(object sender, EventArgs e)
        {
            //TODO: disable audio, animations here
        }

        /// <summary>
        ///  Called when the quit button is clicked - reports the results and closes game
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _quitButton_Click(object sender, RoutedEventArgs e)
        {
            _endTime = DateTime.Now;
            // report results
            _resultsNode.setMoves(_numMoves);
            _resultsNode.setTime(_endTime.Subtract(_startTime));
            nodeList.Add(_resultsNode);

            this.Close();
        }

        /// <summary>
        /// Called when the restart button is clicked - reports game results and restarts game
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _restartButton_Click(object sender, RoutedEventArgs e)
        {
            _endTime = DateTime.Now;
            // report results
            _resultsNode.setMoves(_numMoves);
            _resultsNode.setTime(_endTime.Subtract(_startTime));
            nodeList.Add(_resultsNode);

            MemoryCanvas.Visibility = Visibility.Hidden;
            Board.Visibility = Visibility.Hidden;
            _memory.Visibility = Visibility.Visible;
            end_message.Visibility = Visibility.Hidden;
        }

        /// <summary>
        /// Called when the help button is clicked - displays help video
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _helpButton_Click(object sender, RoutedEventArgs e)
        {
            NoContactCanvas.Visibility = Visibility.Visible;
            // this is just a sample help video (currently a trailed of Kung Fu Panda)
            _helpBox.playMovie("C:/lgts-Images/kung-fu-panda-trailer.mpg");
        }

        /// <summary>
        /// Private inner Card class - models the memory card object
        /// </summary>
        private class Card
        {
            Boolean _facingUp;
            Image _image;
            String _file;
            MemoryGame _game;
            
            public Card(String file, MemoryGame game)
            {
                _file = file;
                _facingUp = false;
                _image = Utils.loadImage("c://lgts-images/cute-star.jpg");
                _image.Visibility = Visibility.Visible;
                _image.Height = 175;
                _image.Width = 175;
                Contacts.AddContactTapGestureHandler(_image, Card_tapHandler);
                _image.MouseDown += Card_clickHandler;
                _game = game;
            }

            /// <summary>
            /// Determines whether the card is facing up
            /// </summary>
            public Boolean isFacingUp()
            {
                return _facingUp;
            }

            /// <summary>
            /// Returns the file path of the image of the card
            /// </summary>
            public String getFile()
            {
                return _file;
            }

            /// <summary>
            /// Flips over the card - changes the source of the image to be displayed on it
            /// </summary>
            public void flipOver()
            {
                if (!_facingUp)
                {
                    Utils.changeSource(_image, _file);
                }
                else
                {
                    Utils.changeSource(_image, "C://lgts-Images/cute-star.jpg");
                }
                _facingUp = !_facingUp;
            }

            /// <summary>
            /// Called when the card is tapped (in order to be flipped over)
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            public void Card_tapHandler(object sender, ContactEventArgs e)
            {
                // if this is neither of the two flipped over cards
                if (_game.getCard1() != this && _game.getCard2() != this)
                {
                    // stop the timer
                    _game._timer.Stop();
                    // if the game is not over and neither of the two cards is null (should always be the case)
                    if (!_game.isEndOfGame() && _game.getCard1() != null && _game.getCard2() != null)
                    {
                        _game._gamePlayed = true;
                        // basically, same as the timer ticking
                        if (_game.isPair(_game.getCard1(), _game.getCard2()))
                        {
                            _game.removeCards(_game.getCard1(), _game.getCard2());
                        }
                        else
                        {
                            _game.getCard1().flipOver();
                            _game.getCard2().flipOver();
                        }
                        _game._card1 = null;
                        _game._card2 = null;
                    }
                    // otherwise, display end message
                    else if (_game.isEndOfGame() && _game._gamePlayed)
                    {
                        _game.end_message.Visibility = Visibility.Visible;
                        _game._gamePlayed = false;
               
                    }
                    // restart the timer
                    _game._timer.Start();
                }

                // if it's not already facing up and there aren't two other cards facing up, flip over the card
                if (!_facingUp && !_game.are2CardsUp())
                {
                    this.flipOver();
                    _game.setCard(this);
                }
            }

            /// <summary>
            /// Called when the card is clicked on (for testing purposes only)
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            public void Card_clickHandler(object sender, EventArgs e)
            {

                if (_game.getCard1() != this && _game.getCard2() != this)
                {
                    _game._timer.Stop();
                    _game._timer.Start();
                }

                if (!_facingUp && !_game.are2CardsUp())
                {
                    this.flipOver();
                    _game.setCard(this);
                }
            }

            /// <summary>
            /// Adds the card to the canvas passed in at the specified location
            /// </summary>
            /// <param name="c"></param>
            /// <param name="location"></param>
            public void addToCanvas(Canvas c, Point location)
            {
                c.Children.Add(_image);
                Canvas.SetTop(_image, location.Y);
                Canvas.SetLeft(_image, location.X);
            }

            /// <summary>
            /// Removes the card from the canvas passed in
            /// </summary>
            /// <param name="c"></param>
            public void removeFromCanvas(Canvas c)
            {
                c.Children.Remove(_image);
            }

            /// <summary>
            /// Sets the dimensions of the card to those passed in
            /// </summary>
            /// <param name="height"></param>
            /// <param name="width"></param>
            public void setDim(int height, int width)
            {
                _image.Height = height;
                _image.Width = width;
            }
        }

        
    }

    /// <summary>
    /// Inner class to help report results of the game to the database - intended for the progress report
    /// </summary>
    public class MemorySessionNode : GameStatNode
    {

        private TimeSpan _time;
        private double _moves;
        private int _difficulty;

        public MemorySessionNode() { }

        public void setTime(TimeSpan t) { _time = t; }
        public TimeSpan getTime() { return _time; }

        public void setMoves(double m) { _moves = m; }
        public double getMoves() { return _moves; }

        public void setDifficulty(int d) { _difficulty = d; }
        public int getDifficutly() { return _difficulty; }

        public void storeToDatabase()
        {
            Database.writeMemorySessionData(this);
        }
    }

}